using System;
using System.Reflection;

namespace Sedna.Core.Common.Objects.Navigation
{
	/// <summary>
	/// Represents node that navigates to object's property or public field.
	/// </summary>
	[Serializable]
	internal class PropertyOrFieldNode : NavigationNode
	{
	    
	    
		private const BindingFlags BINDING_FLAGS
			= BindingFlags.Public | BindingFlags.NonPublic
			| BindingFlags.Instance | BindingFlags.Static
			| BindingFlags.IgnoreCase;

		private string nodeExpression;

		public PropertyOrFieldNode(NavigationNode parent, string nodeExpression, Type nodeType): base(parent, nodeExpression, nodeType)
		{
			this.nodeExpression=nodeExpression;
		}

		protected override object GetValueRecursive(bool autoCreate)
		{
			object context=parent.GetValue(autoCreate);
			MemberInfo node=NodeMemberInfo(context);

			if (IsProperty(node))
			{
				return ((PropertyInfo) node).GetValue(context, null);
			}
			else
			{
				return ((FieldInfo) node).GetValue(context);
			}
		}

		/// <summary>
		/// Sets node's value for the given context.
		/// </summary>
		/// <param name="newValue">New value for this node.</param>
		protected override void SetValue(object newValue)
		{
			object context=parent.GetValue(true); 
			MemberInfo node=NodeMemberInfo(context);

			IValueWrapper wrapper = newValue as IValueWrapper;
			if (IsProperty(node))
			{
                if (!((PropertyInfo)node).CanWrite)
                {
                    throw new InvalidOperationException(String.Format("Property \"{0}\" cannot be written", nodeExpression));
                }
 //				if (((PropertyInfo) node).CanWrite)
//				{
					((PropertyInfo) node).SetValue(context, wrapper==null?newValue:wrapper.GetValue(((PropertyInfo)node).PropertyType), null);
//				} 
			}
			else
			{
				((FieldInfo) node).SetValue(context, wrapper==null?newValue:wrapper.GetValue(((FieldInfo) node).FieldType));
			}
		}

		private bool IsProperty(MemberInfo node)
		{
			return node is PropertyInfo;
		}

		private MemberInfo NodeMemberInfo(object context)
		{
            MemberInfo node = null;

            try
            {
                node = context.GetType().GetProperty(nodeExpression, BINDING_FLAGS);

                if (node == null)
                {
                    node = context.GetType().GetField(nodeExpression, BINDING_FLAGS);
                }
            }
		    catch (System.Reflection.AmbiguousMatchException e)
            {
                # region Added for supporting redefined members in child classes
                
                MemberInfo[] nodes = context.GetType().GetProperties(BINDING_FLAGS);

                Type TypeToCompare = context.GetType();

                while (TypeToCompare != null)
                {
                    for (int i = 0; i < nodes.Length; i++)
                    {
                        if (nodes[i].Name == nodeExpression && nodes[i].DeclaringType == TypeToCompare)
                        {
                            if (node == null)
                                node = nodes[i];
                            else
                                throw new System.Reflection.AmbiguousMatchException(string.Format("More than one '{0}' has been found in the type '{1}'", nodeExpression, TypeToCompare));
                        }
                    }

                    if (node != null)
                        break;
                    else
                        TypeToCompare = TypeToCompare.BaseType;
                }		       

                if (node == null)
                {
                    nodes = context.GetType().GetFields(BINDING_FLAGS);

                    TypeToCompare = context.GetType();

                    while (TypeToCompare != null)
                    {
                        for (int i = 0; i < nodes.Length; i++)
                        {
                            if (nodes[i].Name == nodeExpression && nodes[i].DeclaringType == TypeToCompare)
                            {
                                if (node == null)
                                    node = nodes[i];
                                else
                                    throw new System.Reflection.AmbiguousMatchException(string.Format("More than one '{0}' has been found in the type '{1}'", nodeExpression, TypeToCompare));
                            }
                        }

                        if (node != null)
                            break;
                        else
                            TypeToCompare = TypeToCompare.BaseType;
                    }
                
                }

                # endregion
            }
		    
			return node;
	
		}
	}
}